'use strict';

Object.defineProperty(exports, "__esModule", {
	value: true
});

var _fsExtra = require('fs-extra');

var _path = require('path');

var _webpack = require('webpack');

var _webpackSources = require('webpack-sources');

var _globby = require('globby');

var _globby2 = _interopRequireDefault(_globby);

var _lodash = require('lodash');

var _MultiEntryPlugin = require('webpack/lib/MultiEntryPlugin');

var _MultiEntryPlugin2 = _interopRequireDefault(_MultiEntryPlugin);

var _SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');

var _SingleEntryPlugin2 = _interopRequireDefault(_SingleEntryPlugin);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

const CommonsChunkPlugin = _webpack.optimize.CommonsChunkPlugin;

const globalVar = 'global';
const windowVar = 'window';

const stripExt = path => {
	var _parse = (0, _path.parse)(path);

	const dir = _parse.dir,
	      name = _parse.name;

	return (0, _path.join)(dir, name);
};

class WXAppPlugin {
	constructor() {
		let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

		this.try = handler => (() => {
			var _ref = _asyncToGenerator(function* (arg, callback) {
				try {
					yield handler(arg);
					callback();
				} catch (err) {
					callback(err);
				}
			});

			return function (_x2, _x3) {
				return _ref.apply(this, arguments);
			};
		})();

		this.options = (0, _lodash.defaults)(options || {}, {
			clear: true,
			include: [],
			exclude: [],
			dot: false, // Include `.dot` files
			scriptExt: '.js',
			commonModuleName: 'common.js',
			forceTarget: true,
			assetsChunkName: '__assets_chunk_name__'
		});
		this.options.include = [].concat(this.options.include);
		this.options.exclude = [].concat(this.options.exclude);
	}

	apply(compiler) {
		var _this = this;

		const clear = this.options.clear;

		let isFirst = true;

		this.forceTarget(compiler);

		compiler.plugin('run', this.try((() => {
			var _ref2 = _asyncToGenerator(function* (compiler) {
				yield _this.run(compiler);
			});

			return function (_x4) {
				return _ref2.apply(this, arguments);
			};
		})()));

		compiler.plugin('watch-run', this.try((() => {
			var _ref3 = _asyncToGenerator(function* (compiler) {
				yield _this.run(compiler.compiler);
			});

			return function (_x5) {
				return _ref3.apply(this, arguments);
			};
		})()));

		compiler.plugin('emit', this.try((() => {
			var _ref4 = _asyncToGenerator(function* (compilation) {
				if (clear && isFirst) {
					isFirst = false;
					yield _this.clear(compilation);
				}
			});

			return function (_x6) {
				return _ref4.apply(this, arguments);
			};
		})()));
	}

	forceTarget(compiler) {
		const forceTarget = this.options.forceTarget;
		const options = compiler.options;


		if (forceTarget) {
			if (options.target !== 'web') {
				options.target = 'web';
			}
			if (!options.node || options.node.global) {
				options.node = options.node || {};
				options.node.global = false;
			}
		}
	}

	getBase(compiler) {
		const base = this.options.base;

		if (base) {
			return (0, _path.resolve)(base);
		}

		const compilerOptions = compiler.options;
		const context = compilerOptions.context,
		      entry = compilerOptions.entry;


		const getEntryFromCompiler = () => {
			if (typeof entry === 'string') {
				return entry;
			}

			const appJSRegExp = /\bapp(\.js)?$/;
			const findAppJS = arr => arr.find(path => appJSRegExp.test(path));

			if (Array.isArray(entry)) {
				return findAppJS(entry);
			}
			if (typeof entry === 'object') {

				for (const key in entry) {
					if (!entry.hasOwnProperty(key)) {
						continue;
					}

					const val = entry[key];
					if (typeof val === 'string') {
						return val;
					}
					if (Array.isArray(val)) {
						return findAppJS(val);
					}
				}
			}
		};

		const entryFromCompiler = getEntryFromCompiler();

		if (entryFromCompiler) {
			return (0, _path.dirname)(entryFromCompiler);
		}

		return context;
	}

	getEntryResource() {
		var _this2 = this;

		return _asyncToGenerator(function* () {
			const appJSONFile = (0, _path.resolve)(_this2.base, 'app.json');

			var _ref5 = yield (0, _fsExtra.readJson)(appJSONFile),
			    _ref5$pages = _ref5.pages;

			const pages = _ref5$pages === undefined ? [] : _ref5$pages;
			var _ref5$components = _ref5.components;
			const components = _ref5$components === undefined ? [] : _ref5$components;

			return ['app'].concat(pages).concat(components);
		})();
	}

	getFullScriptPath(path) {
		const base = this.base,
		      scriptExt = this.options.scriptExt;

		return (0, _path.resolve)(base, path + scriptExt);
	}

	clear(compilation) {
		return _asyncToGenerator(function* () {
			const path = compilation.options.output.path;

			yield (0, _fsExtra.remove)(path);
		})();
	}

	addEntries(compiler, entries, chunkName) {
		compiler.apply(new _MultiEntryPlugin2.default(this.base, entries, chunkName));
	}

	compileAssets(compiler) {
		var _this3 = this;

		return _asyncToGenerator(function* () {
			var _options = _this3.options;
			const include = _options.include,
			      exclude = _options.exclude,
			      dot = _options.dot,
			      assetsChunkName = _options.assetsChunkName,
			      scriptExt = _options.scriptExt,
			      entryResources = _this3.entryResources;


			compiler.plugin('compilation', function (compilation) {
				compilation.plugin('before-chunk-assets', function () {
					const assetsChunkIndex = compilation.chunks.findIndex(function (_ref6) {
						let name = _ref6.name;
						return name === assetsChunkName;
					});
					if (assetsChunkIndex > -1) {
						compilation.chunks.splice(assetsChunkIndex, 1);
					}
				});
			});

			const patterns = entryResources.map(function (resource) {
				return `${resource}.*`;
			}).concat(include);

			const entries = yield (0, _globby2.default)(patterns, {
				cwd: _this3.base,
				nodir: true,
				realpath: true,
				ignore: [`**/*${scriptExt}`].concat(_toConsumableArray(exclude)),
				dot
			});

			_this3.addEntries(compiler, entries, assetsChunkName);
		})();
	}

	applyCommonsChunk(compiler) {
		const commonModuleName = this.options.commonModuleName,
		      entryResources = this.entryResources;


		const scripts = entryResources.map(this.getFullScriptPath.bind(this));

		compiler.apply(new CommonsChunkPlugin({
			name: stripExt(commonModuleName),
			minChunks: (_ref7) => {
				let resource = _ref7.resource;

				if (resource) {
					return (/\.js$/.test(resource) && scripts.indexOf(resource) < 0
					);
				}
				return false;
			}
		}));
	}

	addScriptEntry(compiler, entry, name) {
		compiler.plugin('make', (compilation, callback) => {
			const dep = _SingleEntryPlugin2.default.createDependency(entry, name);
			compilation.addEntry(this.base, dep, name, callback);
		});
	}

	compileScripts(compiler) {
		this.applyCommonsChunk(compiler);
		this.entryResources.filter(resource => resource !== 'app').forEach(resource => {
			const fullPath = this.getFullScriptPath(resource);
			this.addScriptEntry(compiler, fullPath, resource);
		});
	}

	toModifyTemplate(compilation) {
		const commonModuleName = this.options.commonModuleName;
		const jsonpFunction = compilation.options.output.jsonpFunction;

		const commonChunkName = stripExt(commonModuleName);

		// inject chunk entries
		compilation.chunkTemplate.plugin('render', (core, _ref8) => {
			let name = _ref8.name;

			if (this.entryResources.indexOf(name) >= 0) {
				const relativePath = (0, _path.relative)((0, _path.dirname)(name), `./${commonModuleName}`);
				const posixPath = relativePath.replace(/\\/g, '/');
				const jsonpRegExp = new RegExp(jsonpFunction);
				const source = core.source();
				const injectContent = `require("./${posixPath}");${globalVar}.`;

				if (source.indexOf(injectContent) < 0) {
					var _jsonpRegExp$exec = jsonpRegExp.exec(source);

					const index = _jsonpRegExp$exec.index;

					const replaceSource = new _webpackSources.ReplaceSource(core);
					replaceSource.insert(index, injectContent);
					return replaceSource;
				}
			}
			return core;
		});

		// replace `window` to `global` in common chunk
		compilation.mainTemplate.plugin('bootstrap', (source, chunk) => {
			const windowRegExp = new RegExp(windowVar, 'g');
			if (chunk.name === commonChunkName) {
				return source.replace(windowRegExp, globalVar);
			}
			return source;
		});

		// override `require.ensure()`
		compilation.mainTemplate.plugin('require-ensure', () => 'throw new Error("Not chunk loading available");');
	}

	run(compiler) {
		var _this4 = this;

		return _asyncToGenerator(function* () {
			_this4.base = _this4.getBase(compiler);
			_this4.entryResources = yield _this4.getEntryResource();

			compiler.plugin('compilation', _this4.toModifyTemplate.bind(_this4));

			_this4.compileScripts(compiler);
			yield _this4.compileAssets(compiler);
		})();
	}
}
exports.default = WXAppPlugin;
module.exports = exports['default'];
